﻿using System;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Loki.Data;
using Loki.Utils;
using Luna.Data.SqlServer.Resources;

namespace Luna.Data.SqlServer
{
    public class LocalSqlRepositoryManager : LokiObject, IRepositoryManager, IDisposable
    {
        private const string CutRequest = @"USE [master]
GO
IF EXISTS (SELECT * FROM sys.databases where name = N'{0}')
ALTER DATABASE [{0}] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
IF EXISTS (SELECT * FROM sys.databases where name = N'{0}')
EXEC master.dbo.sp_detach_db @dbname = N'{0}'
GO";

        private const string DefaultNamespace = "Luna.Data.SqlServer.";
        private const string NewFileName = "new.mdf";
        private const string LogFileSuffix = "_log.ldf";
        private const string LocalSqlSource = @"(Localdb)\V11.0";
        private const string RequestDir = "Requests";
        private const string RequestExtension = ".sql";

        public IDatabaseAdapter Current
        {
            get;
            set;
        }

        #region Dispose

        /// <summary>
        /// Releases all resources used by an instance of the <see cref="LocalSqlRepositoryManager" />
        /// class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in
        /// <strong>true</strong>, and then suppresses finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool disposed = false;

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the
        /// <see cref="LocalSqlDatabaseManager" /> class and optionally releases the managed
        /// resources.
        /// </summary>
        /// <param name="disposing"><strong>true</strong> to release both managed and unmanaged
        /// resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                DetachCurrent();
                disposed = true;
            }
        }

        ~LocalSqlRepositoryManager()
        {
            Dispose(false);
        }

        #endregion Dispose

        /// <summary>
        /// Creates a new database with the specified name.
        /// </summary>
        /// <param name="databaseName">Name of the database.</param>
        public void Create(string databaseName)
        {
            FileStream databaseStream = null;
            try
            {
                databaseStream = File.Create(databaseName);
                Stream databaseFile = Assembly.GetExecutingAssembly().GetManifestResourceStream(DefaultNamespace + NewFileName);
                databaseFile.CopyTo(databaseStream);
                databaseStream.Close();
                databaseFile.Close();
            }
            finally
            {
                if (databaseStream != null)
                {
                    databaseStream.Dispose();
                }
            }

            InitializeConnection(databaseName);
        }

        /// <summary>
        /// Opens the specified database name.
        /// </summary>
        /// <param name="databaseName">Name of the  database.</param>
        public void Open(string databaseName)
        {
            if (File.Exists(databaseName))
            {
                try
                {
                    // detach current database
                    DetachCurrent();

                    InitializeConnection(databaseName);
                }
                catch (SqlException ex)
                {
                    throw BuildErrorFormat<LokiException>(Errors.DatabaseManager_ConnectionError, ex, databaseName);
                }
            }
            else
            {
                throw BuildErrorFormat<LokiException>(Errors.DatabaseManager_FileNotFound, databaseName);
            }
        }

        /// <summary>
        /// Saves the current database with the specified name.
        /// </summary>
        /// <param name="databaseName">Name of the database.</param>
        public void SaveAs(string databaseName)
        {
            SqlConnectionStringBuilder connectionStringParser = new SqlConnectionStringBuilder(Current.ConnectionString);
            if (!string.IsNullOrEmpty(connectionStringParser.AttachDBFilename))
            {
                try
                {
                    if (File.Exists(databaseName))
                    {
                        File.Delete(databaseName);
                    }

                    // detach current database
                    DetachCurrent();

                    File.Copy(connectionStringParser.AttachDBFilename, databaseName);

                    InitializeConnection(databaseName);
                }
                catch (SqlException ex)
                {
                    throw BuildErrorFormat<LokiException>(Errors.DatabaseManager_ConnectionError, ex, databaseName);
                }
            }
            else
            {
                throw BuildError<LokiException>(Errors.DatabaseManager_NoCurrentDatabase);
            }
        }

        /// <summary>
        /// Disconnect the current database.
        /// </summary>
        public void Disconnect()
        {
            DetachCurrent();
            Current.ConnectionString = null;
        }

        private void DetachCurrent()
        {
            if (Current != null && !string.IsNullOrEmpty(Current.ConnectionString))
            {
                // detach database
                SqlConnectionStringBuilder connectionStringParser = new SqlConnectionStringBuilder(Current.ConnectionString);
                string request = string.Format(CultureInfo.InvariantCulture, CutRequest, connectionStringParser.AttachDBFilename);
                try
                {
                    Log.InfoFormat("Detaching {0}", connectionStringParser.AttachDBFilename);
                    Current.ExecuteBatch(request);
                    Current.ConnectionString = null;

                    // remove log file
                    string fileName = Path.GetFileNameWithoutExtension(connectionStringParser.AttachDBFilename);
                    string logFile = Path.Combine(Path.GetDirectoryName(connectionStringParser.AttachDBFilename), fileName + LogFileSuffix);
                    if (File.Exists(logFile))
                    {
                        File.Delete(logFile);
                    }
                }
                catch (Exception ex)
                {
                    Log.ErrorFormat(ex.Message, ex);
                }
            }
        }

        private void InitializeConnection(string databaseName)
        {
            if (Current == null)
            {
                throw BuildError<LokiException>(Errors.DatabaseManager_NoCurrentDatabase);
            }

            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.DataSource = LocalSqlSource;
            builder.IntegratedSecurity = true;
            builder.PersistSecurityInfo = false;
            builder.AttachDBFilename = Path.GetFullPath(databaseName);
            Current.ConnectionString = builder.ConnectionString;

            InitializeRequests();

            Log.InfoFormat(LogStrings.DatabaseManager_Connect, Current.DataSourceDescription);
        }

        private void InitializeRequests()
        {
            var currentAssembly = Assembly.GetExecutingAssembly();
            foreach (var resourceName in currentAssembly.GetManifestResourceNames().Where(x => x.EndsWith(RequestExtension) && x.Contains(RequestDir)))
            {
                using (var resourceStream = currentAssembly.GetManifestResourceStream(resourceName))
                using (var reader = new StreamReader(resourceStream))
                {
                    string procedureCode = reader.ReadToEnd();
                    Current.ExecuteBatch(procedureCode);
                }
            }
        }
    }
}